// popen.hpp

/**
 * \mainpage
 * popen++ library


 *
 * @file
 * This file containts definition popen utilites
 **/

#ifndef POPEN_HPP_GUARD
#define POPEN_HPP_GUARD

#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/operations.hpp>

#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__CYGWIN__)
#  define POPENPP_PLATFORM_POSIX
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
#  define POPENPP_PLATFORM_WIN32
#endif

#if defined(POPENPP_PLATFORM_POSIX) && defined(__GNUC__)
#  define POPENPP_EXPORT __attribute__ ((visibility("default")))
#elif defined(POPENPP_PLATFORM_WIN32)
#  ifdef POPENPP_SHARED
#    ifdef POPENPP_BUILD
#      define POPENPP_EXPORT __declspec(dllexport)
#    else
#      define POPENPP_EXPORT __declspec(dllimport)
#    endif
#  endif
#endif

#ifndef POPENPP_EXPORT
# define POPENPP_EXPORT
#endif


namespace process {

  /**
   * The popen class is used to interaction with child process
   **/
  class  POPENPP_EXPORT popen
  {
  public:
    /// enum different modes for open child process
    enum {
      with_stdin = 1,                  ///< open with replace standart input stream
      with_stdout = 2,                 ///< open with replace standart output stream
      with_stderr = 4,                 ///< open with replace standart error stream
      with_union_stdout_and_stderr = 8 ///< open with merge standart output and error stream
    } ;

    /**
     * Construct proceess_support object,
     * open child process with command line cmd
     *
     * @param cmd The command line for create child process
     * @param flags The mode for replace standart streams
     * @author Moskvitin Andery
     **/
    popen(const char *cmd, int flags);

    /**
     * Construct proceess_support object,
     * open child process with command line cmd
     *
     * @param cmd The command line for create child process
     * @param flags The mode for replace standart streams
     * @author Moskvitin Andery
     **/
    popen(const std::string& cmd, int flags);

    /**
     * Construct copy of proceess_support object
     *
     * @author Krikunov Alexander
     **/
    popen(const popen&);

    /**
     * Check if child process has terminated. Returns returncode attribute.
     *
     * @author Moskvitin Andrey
     **/
    int poll(void) const;

    /**
     *  Wait for child process to terminate. Returns returncode attribute.
     *
     * @author Moskvitin Andrey
     **/
    int wait(void);

    /**
     * The process ID of the child process.
     *
     * @author Moskvitin Andrey
     **/
    int pid(void) const;

    /**
     * If process open with  with_stdin mode,
     * this attribute is a reference on std::ostream object that provides input to the child process.
     * Otherwise, it is "segmantation failed" :-)
     *
     * @author Moskvitin Andrey
     **/
    std::ostream& stdin(void);

    /**
     * If process open with  with_stdout mode,
     * this attribute is a reference on std::ostream object that provides output from the child process.
     * Otherwise, it is "segmantation failed" :-)
     *
     * @author Moskvitin Andrey
     **/
    std::istream& stdout(void);

    /**
     * If process open with  with_stdout mode,
     * this attribute is a reference on std::ostream object that provides output from the child process.
     * Otherwise, it is "segmantation failed" :-)
     *
     * @author Moskvitin Andrey
     **/
    std::istream& stderr(void);

    /**
     * Close input stream of the child process (send EOF code)

     *
     * @author Moskvitin Andrey
     **/
    void close_stdin(void);


    friend class input_filter;

    /**
     * The input_filter class is used for presentation popen object as input filter
     * (look boost::iostreams library for detail)
     **/

    class POPENPP_EXPORT input_filter
    {
    public:

      typedef char                        char_type;
      typedef boost::iostreams::multichar_input_filter_tag  category;

      input_filter( popen& host ): hostClosed( false ), host_(host) { }

      template<typename Source>
      std::streamsize read( Source& src, char* buffer, std::streamsize size )
      {
        while (true) {
          int  readed = boost::iostreams::read(src, buffer, size);
          if (readed >= 0) {
            host_.stdin().write(buffer, readed);
            if (host_.stdout().rdbuf()->in_avail() > 0) {
              int readOut = boost::iostreams::read( host_.stdout(), buffer, size );
              if (readOut > 0)
                return readOut;
            }
          }
          else {
            if (!hostClosed) {
              host_.close_stdin();
              hostClosed = true;
            }

            return boost::iostreams::read(host_.stdout(), buffer, size);
          }
        }
      }

    private:
      bool   hostClosed;
      popen& host_;
    };


    /**
     * Use child process as filter
     *
     * @author Galystyan Sergey
     **/
    input_filter in_filter()
    {
      return input_filter( *this );
    }

    /**
     * Destruct proceess_support object
     *
     * @author Krikunov Alexander
     **/
    ~popen();

  private:
    struct impl;
    impl& pimpl;
  };

  /**
   * Construct popen object with mode with_stdin | with_stdout
   *
   * @param cmd Command line for create child process
   * @author Moskvitin Andrey
   **/
  popen POPENPP_EXPORT popen2(const char *cmd);

  /**
   * Construct popen object with mode with_stdin | with_stdout | with_stderr
   *
   * @param cmd Command line for create child process
   * @author Moskvitin Andrey
   **/
  popen POPENPP_EXPORT popen3(const char *cmd);

  /**
   * Construct popen object with mode with_stdin | with_union_stdout_and_stderr
   *
   * @param cmd Command line for create child process
   * @author Moskvitin Andrey
   **/
  popen POPENPP_EXPORT popen4(const char *cmd);


  void POPENPP_EXPORT transfer(popen& proc, std::istream& in, std::ostream& out);


} // namespace process

#endif
